﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Configuration;
using System.Web.Caching;
using System.Collections;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using FutureVision.Infrastructure;
using System.Text;
using System.Security.Cryptography;

namespace VietThinks
{
    public class CacheManager
    {
        //configuration
        private static string CachePath
        {
            get
            {
                return HttpContext.Current.Server.MapPath(ConfigurationManager.AppSettings["PathToCache"]);
            }
        }

        private static bool isCache
        {
            get
            {
                return (HttpContext.Current.Request.Url.AbsoluteUri.Contains("vietthink.vn")
                       || ConfigurationManager.AppSettings["IsProduction"] == "false") ? true : false;
            }
        }

        public static void Store(string key, object data, Repository repository)
        {
            if (isCache)
            {
                if (repository == Repository.Memory)
                {
                    StoreToMemory(key, data);
                }
                else
                {
                    Store(key, data);
                }
            }

        }

        public static T Get<T>(string key, Repository repository)
        {
            return repository == Repository.Memory
                       ? GetFromMemory<T>(key)
                       : Get<T>(key);
        }

        public static T Get<T>(string key, Func<T> dataToStore, Repository repository)
        {
            T datas = repository == Repository.Memory
                           ? GetFromMemory<T>(key)
                           : Get<T>(key);
            if (datas == null || datas.Equals(default(T)))
            {
                datas = dataToStore();
                if (datas != null)
                    Store(key, datas, repository);
            }
            return datas;
        }

        #region Store to memory

        /// <summary>
        /// Add item into memory cache
        /// </summary>
        private static void StoreToMemory(string key, object data)
        {

            if (HttpContext.Current.Cache[key] == null)
            {
                HttpContext.Current.Cache.Add(Hash(key), data, null,
                                              DateTime.Now.AddDays(Double.Parse("1")),
                                              Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
            }

        }

        /// <summary>
        /// Get cached item
        /// </summary>
        private static T GetFromMemory<T>(string key)
        {
            var cached = HttpContext.Current.Cache[Hash(key)];

            if (cached != null && cached is T)
            {
                return (T)cached;
            }

            return default(T);
        }

        public static void Destroy(string key)
        {
            HttpContext.Current.Cache.Remove(Hash(key));
        }

        public static void DestroyAll()
        {
            var cache = HttpContext.Current.Cache;

            foreach (DictionaryEntry entry in cache)
            {
                HttpContext.Current.Cache.Remove((string)entry.Key);
            }
        }

        public static void DestroyApllicationCache()
        {

            IDictionaryEnumerator enumerator = HttpRuntime.Cache.GetEnumerator();
            List<string> keys = new List<string>();

            while (enumerator.MoveNext())
            {
                keys.Add(enumerator.Key.ToString());
            }

            for (int i = 0; i < keys.Count; i++)
            {
                HttpRuntime.Cache.Remove(keys[i]);
            }
        }

        #endregion

        #region Store to File

        /// <summary>
        /// Store item into file
        /// </summary>
        private static void Store(string key, object data)
        {
            if (isCache && !string.IsNullOrEmpty(key))
            {
                if (!Exists(key) && data != null)
                {
                    Directory.CreateDirectory(CachePath);

                    using (var stream = File.Create(Path.Combine(CachePath, Hash(key))))
                    {
                        var formatter = new BinaryFormatter();

                        formatter.Serialize(stream, data);
                    }
                }
            }
        }

        /// <summary>
        /// Get cached item from file
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        private static T Get<T>(string key)
        {
            if (Exists(key))
            {
                using (var stream = File.OpenRead(Path.Combine(CachePath, Hash(key))))
                {
                    var formatter = new BinaryFormatter();

                    var obj = formatter.Deserialize(stream);

                    if (obj != null && obj is T)
                    {
                        return (T)obj;
                    }
                }
            }

            return default(T);
        }

        public static bool DeleteAll()
        {
            var cachePath = HttpContext.Current.Server.MapPath(ConfigurationManager.AppSettings["PathToCache"]);
            try
            {
                LogService.Administrative.Info("[*] Try to wipe out cache folder [/Cache]...");

                var files = Directory.GetFiles(cachePath);
                foreach (var file in files)
                {
                    File.Delete(file);
                }
                LogService.Administrative.Info("Cache folder [/Cache] has been wiped out.");

                return true;
            }
            catch (DirectoryNotFoundException nfex)
            {
                LogService.Administrative.Warn("Cache folder [/Cache] doesn't existed, it has been wiped out already. {0}", nfex.Message);

                return true;
            }
            catch (Exception ex)
            {
                LogService.Administrative.Warn("Cache folder [/Cache] has NOT been wiped out. Error: {0}", ex.Message);
                LogService.Administrative.Info("[*] Try to remove cache files [/Cache/*]...");

                bool hasErrors = false;

                var files = Directory.GetFiles(cachePath);

                foreach (var file in files)
                {
                    try
                    {
                        File.Delete(file);
                    }
                    catch (Exception ex2)
                    {
                        hasErrors = true;

                        LogService.Administrative.Warn("Error: {0}", ex2.Message);
                    }
                }

                if (hasErrors)
                {
                    LogService.Administrative.Error("Failed to remove cache files [/Cache/*].");
                }
                else
                {
                    LogService.Administrative.Info("Cache files [/Cache/*] have been removed.");
                }

                return !hasErrors;
            }
        }

        private static bool Exists(string key)
        {
            if (!string.IsNullOrEmpty(key))
            {
                var pathToCache = string.Format("{0}\\{1}", CachePath, Hash(key));

                return File.Exists(pathToCache);
            }

            return false;
        }

        private static string Hash(string input)
        {
            try
            {
                byte[] originalBytes = Encoding.UTF8.GetBytes(input.ToLower());

                MD5 encryptType = new MD5CryptoServiceProvider();

                byte[] encodedBytes = encryptType.ComputeHash(originalBytes);

                return BitConverter.ToString(encodedBytes).Replace("-", "").ToLower();
            }
            catch
            {
                return string.Empty;
            }
        }
        #endregion
    }


    public enum CachingKey
    {
        //product browsing
        TopLevelCategories,
        TopLevelCategoriesIncludePromotion,
        PromotionTopCategory,
        ClearanceCategory,
        GlobalNavigationMenu,
        BuyingGuideCategories,
        BrandList,
        IgnoredBotList
    }

    public enum Repository
    {
        Memory,
        Disk
    }
}
